<?php
namespace org\crawlgenie\application\database;
use \Logger;
use \CGException;
use PDO;

/**********************************************************************
 *  DatabasePDO is a database access layer based on PDO. 
 *
 *  @author $Author$:
 *  $Date$:
 *  $Id$:
 *  @version $Rev$:
 *  @package crawlgenie.application
 *  @subpackage database
 */

/*********************************************************************/

$errorArray = array (1 => 'Require $dsn and $user and $password to create a connection');

/**********************************************************************
*/


class DatabasePDO extends DatabaseCore {
    private $dbh;
    private $dsn;
    private $user;
    private $password;
    public $col_info;
    

    
    
    private $transactionInProgress = false;

    /**********************************************************************
    *  Constructor - allow the user to perform a quick connect at the
    *  same time as initialising the DatabasePDO class
    */

        public function __construct($dsn='', $user='', $password='') {
            $this->dsn = $dsn;
            $this->user = $user;
            $this->password = $password;

                // Turn on track errors
                ini_set('track_errors',1);

                if(!class_exists('PDO')) {
                    throw new CGException('PDO library not found in this PHP installation. Please check your PHP Configuration.');

                } else {
                    if($dsn && $user && $password) {
                        $this->connect($dsn, $user, $password);
                    }
                }
        }

        /**********************************************************************
        *  Try to connect to the database server
        */

        protected function connect($dsn='', $user='', $password='')
        {
            global $errorArray; $returnVal = false;

            // Must have a user and a password
            if ( !$dsn || !$user || !$password )
            {
                $this->registerError($errorArray[1].' in '.__FILE__.' on line '.__LINE__);
                $this->show_errors ? trigger_error($errorArray[1], E_USER_WARNING) : null;
            }

            // Establish PDO connection
            try
            {
//                $this->dbh = new PDO($dsn, $user, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8", PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION));
                $this->dbh = new PDO($dsn, $user, $password);
				$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                $returnVal = true;
            }
            catch (PDOException $e)
            {
//                        print 'Unable to connect within the pdo class connect method, :'.$dsn.' '. $user.' ' . $password;
                Logger::getLogger(__CLASS__)->fatal(__METHOD__.': '.$e->getMessage() . ' ' . $e->getTraceAsString());

                $this->registerError($e->getMessage());
                $this->show_errors ? trigger_error($e->getMessage(),E_USER_WARNING) : null;
            }

            return $returnVal;
        }

        /**********************************************************************
        *  In the case of PDO quick_connect is not really needed
        *  because std. connect already does what quick connect does -
        *  but for the sake of consistency it has been included
        */
        private function quick_connect($dsn='', $user='', $password='')
        {
                return $this->connect($dsn, $user, $password);
        }


        /**
         *
         *  Destroys the connection object
         */
        protected function close() {
            $this->dbh = null;
        }

        /**********************************************************************
        *  Reinitialize the connection if it's been destroyed or hasn't been created
        */
        protected function init() {
            if(is_null($this->dbh)) {
                $this->connect($this->dsn, $this->user, $this->password);
            }
        }


        /**
         *  Format a SQLite string correctly for safe SQLite insert
         *  (no mater if magic quotes are on or not)
         *
         *  @@deprecated    Utilize preparedQuery instead
         */
        public function escape($str)
        {
            $this->init();
                switch (gettype($str))
                {
                    case 'string' :
                        $str = $this->dbh->quote($str);
                    break;
                    case 'boolean' :
                        $str = ($str === FALSE) ? 0 : 1;
                    break;
                    default :
                        $str = ($str === NULL) ? 'NULL' : $str;
                    break;
                }
            $this->close();
            return $str;
        }

        /**********************************************************************
        *  Return SQLite specific system date syntax
        *  i.e. Oracle: SYSDATE Mysql: NOW()
        */

        public function sysdate()
        {
                return "NOW()";
        }

        /**********************************************************************
        *  Hooks into PDO error system and reports it to user
        */

        function catch_error($sth = null)
        {

                $error_str = 'No error info';
                $this->init();
                $err_array = $this->dbh->errorInfo();
                $errorCode = $sth->errorCode();

                if(!is_null($sth)) {
                    $err_array = array_merge($err_array, $sth->errorInfo());
                }

                // Note: Ignoring error - bind or column index out of range
//                if(isset($err_array[1]) && $err_array[1] != 25 && $err_array[1] != '00000') {
                if($errorCode != '00000') {

                        $error_str = '';
                        foreach($err_array as $entry) {
                            $error_str .= $entry . ', ';
                        }

                        $error_str = substr($error_str,0,-2);

//                        $this->registerError($error_str);
//                        $this->show_errors ? trigger_error($error_str.' '.$this->last_query, E_USER_WARNING) : null;

                        $msg = 'SQL Error/Warning: '.$error_str.' '.$this->last_query;
                        Logger::getLogger(__CLASS__)->error(__METHOD__ .': '. $msg);
                        $exception = new CGException($msg);
                        $exception->CGExit();
                }

        }

        /**********************************************************************
        *  Begin Transaction
        */
        public function beginTransaction() {
            $this->init();
            if(!$this->dbh->beginTransaction()) {
                Logger::getLogger(__CLASS__)->error(__METHOD__.': Cannot start transaction, dbh handle: ' . spl_object_hash($this->dbh));
                return false;
            } else {
                $this->transactionInProgress = true;
//                        Logger::getLogger(__CLASS__)->info(__METHOD__.': Transaction Started, dbh handle: ' . spl_object_hash($this->dbh));
                return true;
            }
        }


        /**********************************************************************
        *  End Transaction
        */
        public function endTransaction() {
            $this->init();
            try {
//                        Logger::getLogger(__CLASS__)->info(__METHOD__.': Transaction Ended, dbh handle: ' . spl_object_hash($this->dbh));
                $this->transactionInProgress = false;
                $result = $this->dbh->commit();
                $this->close();
                return $result;

            } catch (PDOException $e) {
                $this->transactionInProgress = true;
                Logger::getLogger(__CLASS__)->fatal(__METHOD__.': '.$e->getMessage() . ' ' . $e->getTraceAsString());
            }
        }


        /**********************************************************************
        *  End Transaction
        */
        public function rollBack() {
            $this->init();
            try {
                return $this->dbh->rollBack();

            } catch (PDOException $e) {
                Logger::getLogger(__CLASS__)->fatal(__METHOD__.': '.$e->getMessage() . ' ' . $e->getTraceAsString());
            }
        }



        /**
         *  Prepared Query
         */
         public function preparedQuery($queryString, $valueArray) {
                $this->init();

                // For reg expressions
                $queryString = str_replace("/[\n\r]/",'',trim($queryString));

                // initialise return
                $return_val = 0;

                // Flush cached values..
                $this->flush();

                // Log how the function was called
                $this->func_call = "\$db->preparedQuery(\"$queryString\")";

                // Keep track of the last query for debug..
                $this->last_query = $queryString;

                $this->num_queries++;


                // Use core file cache function
                $cache = $this->getCache($queryString);
                if ($cache) {
                    // Trace all queries
                    if ( $this->use_trace_log ) {
                        $this->trace_log[] = $this->debug(false);
                    }
                    return $cache;
                }

                // Query was an insert, delete, update, replace
                if ( preg_match("/^(insert|delete|update|replace|drop|create)\s+/i",$queryString)) {
                    // Perform the query and log number of affected rows
                    try {
                        $sth = $this->dbh->prepare($queryString);

//                                Logger::getLogger(__CLASS__)->info(__METHOD__.': Statement object created from dbh handle: ' . spl_object_hash($this->dbh));
                        $sth->execute($valueArray);

                        // If there is an error then take note of it..
                        if ( $this->catch_error($sth) ) return false;

                        $this->rowsAffected = $sth->rowCount();
                        Logger::getLogger(__CLASS__)->debug(__METHOD__.': Rows affected ->'.$this->rowsAffected . ' for '. $queryString);

                    } catch (PDOException $e) {
                        Logger::getLogger(__CLASS__)->fatal(__METHOD__.': '.$e->getMessage() . ' ' . $e->getTraceAsString());
                        return false;

                    } catch (CGException $e) {
                        Logger::getLogger(__CLASS__)->fatal(__METHOD__.': '.$e->getMessage() . ' ' . $e->getTraceAsString());
                        return false;
                    }

                        $isInsert = true;

                        // Take note of the insert_id
                        if ( preg_match("/^(insert|replace)\s+/i",$queryString) )
                        {
                            $this->insert_id = @$this->dbh->lastInsertId();
                        }

                        // Return number for rows affected
                        $return_val = $this->rowsAffected;

                } else {

                    try {

                        $sth = $this->dbh->prepare($queryString);
                        $sth->execute($valueArray);

                        // If there is an error then take note of it..
                        if ( $this->catch_error($sth) ) return false;


                        $this->rowsAffected = $sth->rowCount();

                            
                    } catch (PDOException $e) {
                        Logger::getLogger(__CLASS__)->fatal(__METHOD__.': '.$e->getMessage() . ' ' . $e->getTraceAsString());
                        return false;

                    } catch (CGException $e) {
                        Logger::getLogger(__CLASS__)->fatal(__METHOD__.': '.$e->getMessage() . ' ' . $e->getTraceAsString());
                        return false;
                    }

                        $isInsert = false;

                        $colCount = $sth->columnCount();

                        
                        for ( $i=0 ; $i < $colCount ; $i++ )
                        {
                            $meta = $sth->getColumnMeta($i);
                            
                            if ($meta) {
                                $this->col_info[$i] = new \stdClass();
                                $this->col_info[$i]->name =  !isset($meta['name']) ? null : $meta['name'];
                                $this->col_info[$i]->type =  !isset($meta['native_type']) ? null : $meta['native_type'];
                                $this->col_info[$i]->max_length = '';
                            } else {
                                $this->col_info[$i]->name =  'undefined';
                                $this->col_info[$i]->type =  'undefined';
                                $this->col_info[$i]->max_length = '';
                            }
//                                    print_r($meta);
                        }

                        // Store Query Results
                        $num_rows=0;


                        while($row = @$sth->fetch(PDO::FETCH_ASSOC)) {
                             // Store results as objects within main array
                            $this->last_result[$num_rows] = (object) $row;
                            $num_rows++;
                        }
         //               var_dump($this->last_result);

                        // Log number of rows the query returned
                        $this->num_rows = $num_rows;

                        // Return number of rows selected
                        $return_val = $this->num_rows;

                }

                // disk caching of queries
                $this->storeCache($queryString, $isInsert);

                // If debug ALL queries
                $this->trace || $this->debug_all ? $this->debug() : null ;


                // Trace all queries
                if ( $this->use_trace_log )
                {
                        $this->trace_log[] = $this->debug(false);
                }

                if(!$this->transactionInProgress) {
                    $this->close();
                }

                return $return_val;

         }




        // ==================================================================
        //	Basic Query	- see docs for more detail

        public function query(/*string*/$query)
        {
                $this->init();

                // For reg expressions
                $query = str_replace("/[\n\r]/",'',trim($query));

                // initialise return
                $return_val = 0;

                // Flush cached values..
                $this->flush();

                // Log how the function was called
                $this->func_call = "\$db->query(\"$query\")";

                // Keep track of the last query for debug..
                $this->last_query = $query;

                $this->num_queries++;


                // Use core file cache function
                $cache = $this->getCache($query);
                if ($cache) {
                    // Trace all queries
                    if ( $this->use_trace_log ) {
                        $this->trace_log[] = $this->debug(false);
                    }
                    return $cache;
                }

                // Query was an insert, delete, update, replace
                if ( preg_match("/^(insert|delete|update|replace|drop|create)\s+/i",$query)) {
                    // Perform the query and log number of affected rows
                    $this->rowsAffected = $this->dbh->exec($query);
//                            Logger::getLogger(__CLASS__)->info(__METHOD__.': DML query executed from dbh handle: ' . spl_object_hash($this->dbh));

                    // If there is an error then take note of it..
                    if ( $this->catch_error() ) return false;

                        $isInsert = true;

                        // Take note of the insert_id
                        if ( preg_match("/^(insert|replace)\s+/i",$query) )
                        {
                                $this->insert_id = @$this->dbh->lastInsertId();
                        }

                        // Return number for rows affected
                        $return_val = $this->rowsAffected;

                }
                // Query was an select
                else
                {
                        // Perform the query and log number of affected rows
                        $sth = $this->dbh->query($query);
//                                Logger::getLogger(__CLASS__)->info(__METHOD__.': Select query executed from dbh handle: ' . spl_object_hash($this->dbh));

                        // If there is an error then take note of it..
                        if ( $this->catch_error() ) return false;

                        $isInsert = false;

                        $colCount = $sth->columnCount();

                        for ( $i=0 ; $i < $colCount ; $i++ )
                        {
                            $meta = $sth->getColumnMeta($i);
                            if ($meta) {
                                $this->col_info[$i]->name =  $meta['name'];
                                $this->col_info[$i]->type =  @$meta['native_type'];
                                $this->col_info[$i]->max_length = '';
                            } else {
                                $this->col_info[$i]->name =  'undefined';
                                $this->col_info[$i]->type =  'undefined';
                                $this->col_info[$i]->max_length = '';
                            }
//                                    print_r($meta);
                        }

                        // Store Query Results
                        $num_rows=0;

                        while($row = @$sth->fetch(PDO::FETCH_ASSOC)) {
                            // Store results as objects within main array
                            $this->last_result[$num_rows] = (object) $row;
                            $num_rows++;
                        }
                     //   var_dump($this->last_result);

                        // Log number of rows the query returned
                        $this->num_rows = $num_rows;

                        // Return number of rows selected
                        $return_val = $this->num_rows;

                }

                // disk caching of queries
                $this->storeCache($query, $isInsert);

                // If debug ALL queries
                $this->trace || $this->debug_all ? $this->debug() : null ;


                // Trace all queries
                if ( $this->use_trace_log )
                {
                        $this->trace_log[] = $this->debug(false);
                }

                if(!$this->transactionInProgress) {
                    $this->close();
                }
                return $return_val;

        }




}

?>